#include <stdio.h>
#include <stdlib.h>
#include <mpi.h>
#include <time.h>

#define MASTER 0

int **red, **green, **blue;
int **redF, **greenF, **blueF;

void escreve_imagem(int *bordas, int largura, int **red, int **green, int **blue, FILE *out, int rank)
{


    int i, j;
    for(i=bordas[0]; i<=bordas[1]; i++)
        for(j=0; j<largura; j++)
            fprintf(out, "%d\n%d\n%d\n",red[i][j],green[i][j],blue[i][j]);

    //printf("--processo %d - bordas: %d, %d\n%d-%d-%d\n",rank,bordas[0],bordas[1],red[i-1][j-1],green[i-1][j-1],blue[i-1][j-1]);
}


void smooth(int *bordas, int largura, int altura, int **red, int **green, int **blue, int **redF, int **greenF, int **blueF)
{
    int i, j;
    //percorrendo toda a imagem, exceto a borda
    for(i=bordas[0]; i<=bordas[1]; i++)
        for(j=0; j<largura; j++)
        {

            if(i>1 && i<altura-2 && j>1 && j<largura-2) //se não for pixel de borda, aplica-se o smooth
            {
                redF[i][j] = (red[i-2][j-2] + red[i-1][j-2] + red[i][j-2] + red[i+1][j-2] + red[i+2][j-2]
                              + red[i-2][j-1] + red[i-1][j-1] + red[i][j-1] + red[i+1][j-1] + red[i+2][j-1]
                              + red[i-2][j] + red[i-1][j] + red[i][j] + red[i+1][j] + red[i+2][j]
                              + red[i-2][j+1] + red[i-1][j+1] + red[i][j+1] + red[i+1][j+1] + red[i+2][j+1]
                              + red[i-2][j+2] + red[i-1][j+2] + red[i][j+2] + red[i+1][j+2] + red[i+2][j+2])/25;

                greenF[i][j] = (green[i-2][j-2] + green[i-1][j-2] + green[i][j-2] + green[i+1][j-2] + green[i+2][j-2]
                                + green[i-2][j-1] + green[i-1][j-1] + green[i][j-1] + green[i+1][j-1] + green[i+2][j-1]
                                + green[i-2][j] + green[i-1][j] + green[i][j] + green[i+1][j] + green[i+2][j]
                                + green[i-2][j+1] + green[i-1][j+1] + green[i][j+1] + green[i+1][j+1] + green[i+2][j+1]
                                + green[i-2][j+2] + green[i-1][j+2] + green[i][j+2] + green[i+1][j+2] + green[i+2][j+2])/25;

                blueF[i][j] = (blue[i-2][j-2] + blue[i-1][j-2] + blue[i][j-2] + blue[i+1][j-2] + blue[i+2][j-2]
                               + blue[i-2][j-1] + blue[i-1][j-1] + blue[i][j-1] + blue[i+1][j-1] + blue[i+2][j-1]
                               + blue[i-2][j] + blue[i-1][j] + blue[i][j] + blue[i+1][j] + blue[i+2][j]
                               + blue[i-2][j+1] + blue[i-1][j+1] + blue[i][j+1] + blue[i+1][j+1] + blue[i+2][j+1]
                               + blue[i-2][j+2] + blue[i-1][j+2] + blue[i][j+2] + blue[i+1][j+2] + blue[i+2][j+2])/25;

            }
            else  //se for pixel da borda, mantem o valor original
            {
                redF[i][j] = red[i][j];
                greenF[i][j] = green[i][j];
                blueF[i][j] = blue[i][j];
            }
           
        }	
}

int main(int argc, char* argv[])
{

    FILE *in , *out;

	char *input = argv[1];
	char *output = argv[2];

    char key[128];
    int i,j,altura,largura,max, dim;

	clock_t execution_time;
	
    int *bordas; //vetor de 2 posições que armazenam, respectivamente, o número da primeira e da última linha da imagem onde a thread irá aplicar o smooth
    bordas = (int*)calloc(2,sizeof(int));

    int num_threads;
    int rc, rank, num_tasks, tag, dest, source=0;

	unsigned char temp1, temp2, temp3;

    in = fopen(input, "r");
    if(in == NULL)
    {
        printf("Erro na abertura do arquivo in.ppm \n");
        return 0 ;
    }

    out = fopen(output, "w") ;
    if(out == NULL)
    {
        printf("Erro na abertura do arquivo out.ppm\n");
        return 0 ;
    }

	//lendo o tipo de codificação do arquivo
    rc = fscanf(in, "%s", key) ;

    if(strcmp(key,"P3") && strcmp(key,"P6")){
        printf("Arquivo nao e um PPM\n");
        fclose(in);
        return -1;
    }

	//lendo as dimensões da imagem
    rc = fscanf(in, "%d %d %d", &largura, &altura, &max);
    dim = altura*largura;


//alocação dinâmica das matrizes que armazenam os pixels (valores originais e os valores após aplicado o smooth) da imagem 
    red = (int**)malloc(altura*sizeof(int*));
    for(i=0; i<altura; i++)
        red[i] = (int*)malloc(largura*sizeof(int));

    green = (int**)malloc(altura*sizeof(int*));
    for(i=0; i<altura; i++)
        green[i] = (int*)malloc(largura*sizeof(int));

    blue = (int**)malloc(altura*sizeof(int*));
    for(i=0; i<altura; i++)
        blue[i] = (int*)malloc(largura*sizeof(int));


    redF = (int**)malloc(altura*sizeof(int*));
    for(i=0; i<altura; i++)
        redF[i] = (int*)malloc(largura*sizeof(int));

    greenF = (int**)malloc(altura*sizeof(int*));
    for(i=0; i<altura; i++)
        greenF[i] = (int*)malloc(largura*sizeof(int));

    blueF = (int**)malloc(altura*sizeof(int*));
    for(i=0; i<altura; i++)
        blueF[i] = (int*)malloc(largura*sizeof(int));
//--------

    MPI_Status Stat;

    // Leitura dos pixels da imagem
    if(!strcmp(key,"P3")){
    	for(i=0; i<altura; i++)
        	for(j=0; j<largura; j++)
            	rc = fscanf(in, " %d %d %d ", &red[i][j] , &green[i][j] , &blue[i][j]);
	}
	else if(!strcmp(key,"P6")){
    	for(i=0; i < altura; i++){
        	for(j=0; j < largura; j++){
	            fread(&temp1 1, 1, in);
	            fread(&temp2 1, 1, in);
	            fread(&temp3 1, 1, in);
				red[i][j] = (int)temp1;
				green[i][j] = (int)temp2;
				blue[i][j] = (int)temp3;
			}
		}
	}


    rc = MPI_Init (&argc, &argv);      /* starts MPI */

    if(rc == MPI_SUCCESS)
    {
        //printf("\nMPI iniciado");
        MPI_Comm_size(MPI_COMM_WORLD, &num_tasks);
        MPI_Comm_rank(MPI_COMM_WORLD, &rank);
        //printf("Sou o processo %d de um total de %d\n",rank,num_tasks);
    }
    else
        printf("Erro ao iniciar MPI\n");

	//calculando as bordas que devem ser respeitadas por cada processo que aplica o smooth (todos, exceto o MASTER) em um pedaço da imagem
    if(rank==MASTER)
    {
        int altura_inicio=0;
        for(i=1; i<num_tasks-1; i++)
        {
            tag = i;
            dest = i;

            //cálculo das bordas:
            bordas[0] = altura_inicio;
            bordas[1] = altura_inicio + (altura/(num_tasks-1));

            rc = MPI_Send(bordas, 2, MPI_INT, dest, tag, MPI_COMM_WORLD);
            altura_inicio = bordas[1]+1;
        }
        //para a última thread, lê todo o final da imagem:
        tag++;
        dest++;
        bordas[0] = altura_inicio;
        bordas[1] = altura-1;
        rc = MPI_Send(bordas, 2, MPI_INT, dest, tag, MPI_COMM_WORLD);
    }

    //MPI_Barrier(MPI_COMM_WORLD);
/*
 for(i=1; i<=num_tasks; i++)
    {
        if(rank==i)
{
rc = MPI_Recv(bordas, 2, MPI_INT, source, rank, MPI_COMM_WORLD, &Stat);

        smooth(bordas,largura,altura,red,green,blue,redF,greenF,blueF,rank);
        printf("processo %d - bordas: %d, %d\n",rank,bordas[0],bordas[1]);
}

    }*/

MPI_Barrier(MPI_COMM_WORLD); //espera todos os processos para que todos começem a aplicar o smooth ao mesmo tempo, obtendo um tempo mais ideal

if(rank==MASTER) //somente o processo MASTER calcula o tempo de execução
	execution_time = clock();

//todos os processos, exceto o MASTER, irão aplicar, cada um, o smooth sobre um trecho da imagem
    if(rank!=MASTER)
    {   
        rc = MPI_Recv(bordas, 2, MPI_INT, source, rank, MPI_COMM_WORLD, &Stat);

        smooth(bordas,largura,altura,red,green,blue,redF,greenF,blueF);
    }
MPI_Barrier(MPI_COMM_WORLD); //espera todos os processos aplicarem o smooth

//calculando o tempo de execução total, para a aplicação do smooth em toda a imagem:
if(rank==MASTER) //somente o processo Master calcula o tempo de execução
	{
	execution_time = clock() - execution_time; 
printf("\n\n%.6f\n",((float)execution_time)/CLOCKS_PER_SEC);
}

/*MPI_Barrier(MPI_COMM_WORLD); //espera todos os processos
    MPI_Finalize();
return 0;*/

	//escrevendo o cabeçalho
    if(rank==MASTER)
    {
        fprintf(out,"P3\n" );
        fprintf(out, "%d %d\n%d\n", largura, altura, max);
    }
    MPI_Barrier(MPI_COMM_WORLD); //espera escrever o cabeçalho

//###REGIÃO CRÍTICA -> apenas um processo escreve na imagem por vez, obedecendo uma certa ordem que é determinada pela posição dos pixels que cada processo aplicou o smooth
	//escrevendo os pixels da imagem, após aplicado o smooth:
  for(i=1; i<=num_tasks; i++)
    {
		//cada processo escreve sua parte da imagem por vez:
        if(rank==i)
            escreve_imagem(bordas,largura,redF,greenF,blueF, out, rank);
        MPI_Barrier(MPI_COMM_WORLD);
    }
//####fim da Região Crítica

	//fechando ponteiros para arquivos
    fclose(in);
    fclose(out);

	//desalocando memória que era utilizada para armazenar os valores dos pixels da imagem (antes e depois do smooth)
	for(i=0;i<altura ;i++)
	{
		free(red[i]);
		free(green[i]);
		free(blue[i]);

		free(redF[i]);
		free(greenF[i]);
		free(blueF[i]);
	}
		free(red);
		free(green);
		free(blue);

		free(redF);
		free(greenF);
		free(blueF);
	
	free(bordas);

    MPI_Barrier(MPI_COMM_WORLD);
    MPI_Finalize(); /* ends MPI */

    return 0;
}

